package ar.edu.itba.it.cg.grupo01.impl.shape;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import ar.edu.itba.it.cg.grupo01.api.Intersection;
import ar.edu.itba.it.cg.grupo01.api.Ray;
import ar.edu.itba.it.cg.grupo01.api.shape.Shape;
import ar.edu.itba.it.cg.grupo01.impl.IntersectionFactory;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;

public class Sphere extends AbstractShape {

	private Point3d center;
	private double radius;

	public Sphere(final Point3d center, final double radius,
			IntersectionFactory intersectionFactory) {
		this(center,radius,intersectionFactory,null);
	}

	public Sphere(final Point3d center, final double radius,
			IntersectionFactory intersectionFactory, Color color) {
		super(intersectionFactory, color);
		checkNotNull(center, "Sphere center should not be null");
		checkArgument(radius > 0.0, "Radius should be greater than 0.0");
		this.center = center;
		this.radius = radius;
	}

	@Override
	public Intersection<? extends Shape> intersects(final Ray ray) {
		checkNotNull(ray, "Ray should not be null");
		Collection<Double> positiveIntersections = findIntersections(ray);

		if (positiveIntersections.isEmpty()) {
			return intersectionFactory.emptyIntersection();
		}

		List<Point3d> points = new ArrayList<Point3d>();
		for (Double result : positiveIntersections) {

			Point3d point = buildIntersectionPoint(result, ray);
			points.add(point);
		}
		return intersectionFactory.createIntersection(points);
	}

	public Point3d getCenter() {
		return (Point3d) center.clone();
	}

	public double getRadius() {
		return radius;
	}

	private Collection<Double> findIntersections(Ray ray) {
		
		Vector3d dst = new Vector3d(ray.getOrigin());
		dst.sub(center);
		double b = 2 * (ray.getDirection().dot(dst));
		double c = dst.lengthSquared() - (radius * radius);
		double d = b * b - 4 * c;
		if (d < 0) {
			return Collections.emptySet();
		}

		// We take only the positive answers as negative would mean that the
		// intersection is behind the ray
		ImmutableSet<Double> intersections = ImmutableSet.of(
				(-b - Math.sqrt(d)) / 2, (-b + Math.sqrt(d)) / 2);
		Collection<Double> positiveIntersections = Collections2.filter(
				intersections, new Predicate<Double>() {

					@Override
					public boolean apply(Double arg0) {

						return arg0.compareTo(-0.0) >= 0;
					}
				});

		return positiveIntersections;
	}

	private Point3d buildIntersectionPoint(Double result, Ray ray) {

		Point3d base = ray.getOrigin();
		Vector3d direction = ray.getDirection();
		Point3d newDirection = new Point3d(direction.x * result, direction.y
				* result, direction.z * result);
		base.add(newDirection);
		return base;

	}

}